package com.cy.hui_jie_dan.websocket;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

/**
 * 该处理器用于处理WebSocket连接中的心跳包。
 * 它支持处理JSON格式和UTF-8编码文本格式的心跳包。
 */
@Slf4j
public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
    // 用于JSON和Java对象之间的转换
    private static final ObjectMapper objectMapper = new ObjectMapper();
    // 允许丢失的最大心跳包数量
    private static final int MAX_LOST_HEARTBEAT = 10;
    // 记录丢失的心跳包数量
    private int lostHeartbeatCount = 0;
    private int reconnectionAttempts = 0;
    private static final int MAX_RECONNECTION_ATTEMPTS = 5;

    /**
     * 当接收到通道数据时调用此方法。
     * 此方法用于处理接收到的WebSocket帧，并检查是否为心跳消息。
     *
     * @param ctx 通道处理上下文
     * @param msg 接收到的消息对象
     * @throws Exception 如果在处理消息时发生异常
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof WebSocketFrame) {
            WebSocketFrame frame = (WebSocketFrame) msg;
            if (frame instanceof TextWebSocketFrame) {
                String text = ((TextWebSocketFrame) frame).text();
                try {
                    Map<String, Object> message = objectMapper.readValue(text, Map.class);
                    if ("PONG".equals(message.get("type"))) {
                        // Reset lost heartbeat count on successful pong
                        lostHeartbeatCount = 0;
                        return;
                    }
                } catch (Exception e) {
                    log.warn("Failed to parse message as JSON: {}", text);
                }
            }
        }
        ctx.fireChannelRead(msg);
    }

    /**
     * 当通道中发生异常时调用此方法。
     *
     * @param ctx   通道处理上下文
     * @param cause 异常对象
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (cause instanceof java.net.SocketException && "Connection reset".equals(cause.getMessage())) {
            if (reconnectionAttempts < MAX_RECONNECTION_ATTEMPTS) {
                reconnectionAttempts++;
                log.warn("Connection reset detected, attempt {}/{}", reconnectionAttempts, MAX_RECONNECTION_ATTEMPTS);
                // Don't close the connection immediately, let the client try to reconnect
                return;
            }
        }
        log.error("心跳处理异常：{}", cause.getMessage(), cause);
        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.READER_IDLE) {
                lostHeartbeatCount++;
                log.warn("Connection reset detected, attempt {}/{}", lostHeartbeatCount, MAX_LOST_HEARTBEAT);
                
                if (lostHeartbeatCount >= MAX_LOST_HEARTBEAT) {
                    log.error("Too many lost heartbeats, closing connection");
                    ctx.close();
                } else {
                    // Send a ping to check connection
                    Map<String, Object> pingMessage = new HashMap<>();
                    pingMessage.put("type", "PING");
                    pingMessage.put("timestamp", System.currentTimeMillis());
                    ctx.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(pingMessage)));
                }
            } else if (event.state() == IdleState.WRITER_IDLE) {
                // Send a ping message
                Map<String, Object> pingMessage = new HashMap<>();
                pingMessage.put("type", "PING");
                pingMessage.put("timestamp", System.currentTimeMillis());
                ctx.writeAndFlush(new TextWebSocketFrame(objectMapper.writeValueAsString(pingMessage)));
            }
        }
        super.userEventTriggered(ctx, evt);
    }
}